#

import logging
from xml.etree import ElementTree as ET

from wglmkapplayer.ast import \
    ASTRegistry, \
    ASTTypeDecl, \
    ASTEnum, \
    ASTEnumValue, \
    ASTTypeName, \
    ASTParam, \
    ASTFunction

LOGGER = logging.getLogger('wglmkapplayer')


# Parser
#
class Parser :


    # __init__
    #
    def __init__ (self, input_file) :
        self.input_file = input_file
        self.registry = ASTRegistry()


    # check_attrib
    #
    def check_attrib (self, node, keys) :
        for k in node.attrib.keys() :
            if k not in keys :
                assert 0, (node.tag, k, keys, node.attrib)


    # parse
    #
    def parse (self) :
        LOGGER.debug(f"parsing {self.input_file}")
        doc = ET.parse(self.input_file)
        root = doc.getroot()
        assert root.tag == 'registry', root.tag
        self.parse_registry(root)
        return self.registry


    # parse_registry
    #
    def parse_registry (self, node) :
        self.check_attrib(node, ())
        todo = ('feature', 'extensions')
        #LOGGER.warning(f"[TODO] {todo}")
        for child in node :
            if child.tag == 'comment' :
                pass # ignore
            elif child.tag in todo :
                pass # [TODO]
            elif child.tag == 'types' :
                self.parse_types(child)
            elif child.tag == 'enums' :
                enum = self.parse_enums(child)
                self.registry.add_enum(enum)
            elif child.tag == 'commands' :
                self.parse_commands(child)
            else :
                assert 0, f"TODO {child.tag}"


    # parse_types:
    #
    def parse_types (self, node) :
        self.check_attrib(node, ())
        for child in node :
            if child.tag == 'type' :
                type_decl = self.parse_type(child)
                self.registry.add_type_decl(type_decl)
            else :
                assert 0, child.tag


    # parse_type
    #
    def parse_type (self, node) :
        # [TODO] use name attrib?
        self.check_attrib(node, ('comment', 'name', 'requires'))
        name = None
        decl = node.text or ''
        for child in node :
            if child.tag == 'name' :
                assert name is None
                name = child.text
                decl += child.text
                decl += child.tail or ''
            elif child.tag == 'apientry' :
                decl += 'WGL_API'
                decl += child.tail or ''
            else :
                assert 0, child.tag

        return ASTTypeDecl(name, decl)


    # parse_enums
    #
    def parse_enums (self, node) :
        # ignored: vendor, start, end
        self.check_attrib(node, ('comment', 'namespace', 'type', 'group', 'vendor', 'start', 'end'))
        nsname = node.attrib['namespace']
        assert nsname == 'GL', nsname # [TODO] ?
        name = None
        enum_type = node.attrib.get('type', 'none')
        group = node.attrib.get('group', '')
        values = [] # temp
        for child in node :
            if child.tag == 'enum' :
                values.append(self.parse_enum(child))
            elif child.tag == 'unused' :
                pass
            else :
                assert 0, child.tag

        return ASTEnum(name, enum_type, group, values)


    # parse_enum
    #
    def parse_enum (self, node) :
        self.check_attrib(node, ('comment', 'name', 'type', 'group', 'value', 'alias', 'api'))
        name = node.attrib['name']
        value_type = node.attrib.get('type', 'none')
        group = node.attrib.get('group', None)
        value = node.attrib['value']
        api = node.attrib.get('api', 'gl')
        alias = node.attrib.get('alias')
        for child in node :
            assert 0, child.tag

        group = (group.split(',') if group else ())
        value = ASTEnumValue(name, value_type, group, value, alias, api)
        return value
    

    # parse_commands
    #
    def parse_commands (self, node) :
        self.check_attrib(node, ('namespace'))
        nsname = node.attrib['namespace']
        # we have a couple things to fix to handle other namespaces
        assert nsname == 'GL', nsname
        for child in node :
            if child.tag == 'command' :
                func = self.parse_command(child)
                self.registry.add_function(func)
            else :
                assert 0, f"TODO {child.tag}"


    # parse_command
    #
    def parse_command (self, node) :
        self.check_attrib(node, ('comment',))
        name = None
        return_type = None
        params = []
        aliases = []
        for child in node :
            if child.tag == 'proto' :
                assert name is None
                name, return_type = self.parse_proto(child)
            elif child.tag == 'param' :
                params.append(self.parse_param(child))
            elif child.tag == 'glx' :
                pass # [TODO]
            elif child.tag == 'alias' :
                self.check_attrib(child, ('name',))
                aliases.append(child.attrib['name'])
            elif child.tag == 'vecequiv' :
                pass # [TODO]
            else :
                assert 0, child.tag

        # [TODO] check namespace name
        if name[:2] == 'gl' :
            name = name[2:]
        else :
            pass #LOGGER.warning(f"[TODO] fix namespace: '{name}'")
            
        return ASTFunction(name, return_type, params, aliases)
        

    # parse_proto
    #
    def parse_proto (self, node) :
        self.check_attrib(node, ('group', 'class'))
        # [TODO] group, class
        name = None
        ptype = None
        decl = node.text or ''
        for child in node :
            if child.tag == 'name' :
                assert name is None
                name = child.text
            elif child.tag == 'ptype' :
                assert ptype is None
                ptype = child.text
                decl += ptype
            else :
                assert 0, child.tag
            decl += child.tail or ''
        return name, ASTTypeName(ptype, decl)


    # parse_param
    #
    def parse_param (self, node) :
        self.check_attrib(node, ('group', 'class', 'len'))
        # [TODO] group, class, len
        name = None
        ptype = None
        decl = node.text or ''
        for child in node :
            if child.tag == 'name' :
                assert name is None
                name = child.text
            elif child.tag == 'ptype' :
                assert ptype is None
                ptype = child.text
                decl += ptype
            else :
                assert 0, child.tag
            decl += child.tail or ''
        return ASTParam(name, ASTTypeName(ptype, decl))
